use config::Config;
use hash_computation::{fatscdc::*, rabin::*};
use serde::{Deserialize, Serialize};

pub const CHUNKING_CONFIG_NAME:&str = "chunking";

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ChunkingBuilder {
    fixed(FixedBuilder),
    rabin(RabinBuilder),
    normalized_rabin(RabinBuilder),
    fastcdc(FastCDCBuilder),
    accelerated_fastcdc(FastCDCBuilder),
}

impl ChunkingBuilder {
    pub fn build(self) -> ChunkingSelector {
        match self {
            Self::fixed(builder) => ChunkingSelector::Fixed(builder.build()),
            Self::rabin(builder) => ChunkingSelector::Rabin(RabinChunking(builder.build())),
            Self::normalized_rabin(builder) => ChunkingSelector::NormalizedRabin(NormalizedRabinChunking(builder.build())),
            Self::fastcdc(builder) => ChunkingSelector::FastCDC(FastcdcChunking(builder.build())),
            Self::accelerated_fastcdc(builder) => ChunkingSelector::AccelerateFastCDC(AccelerateFastcdcChunking(builder.build())),
        }
    }
}

impl Default for ChunkingBuilder {
    fn default() -> Self {
        Self::fixed(FixedBuilder::new())
    }
}

#[derive(Debug, Clone)]
#[impl_enum::with_methods {
    pub fn min_len(&self) -> usize
    pub fn split(&self, data:&[u8]) -> usize
}]
pub enum ChunkingSelector {
    Fixed(FixedChunking),
    Rabin(RabinChunking),
    NormalizedRabin(NormalizedRabinChunking),
    FastCDC(FastcdcChunking),
    AccelerateFastCDC(AccelerateFastcdcChunking),
}

impl ChunkingSelector {
    pub fn from_config(config:&Config) -> Option<Self> {
        let builder:ChunkingBuilder = config.get(CHUNKING_CONFIG_NAME).ok()?;
        let chunking = builder.build();
        Some(chunking)
    }
}

pub trait SplitChunk:Send + Sync + 'static {
    // const NAME:&str;
    fn min_len(&self) -> usize;
    fn split(&self, data:&[u8]) -> usize;
}


#[derive(Default, Debug, Clone)]
pub struct RabinChunking(pub RabinConfig);

impl SplitChunk for RabinChunking {
    fn min_len(&self) -> usize {
        self.0.min_chunking_len()
    }
    fn split(&self, data:&[u8]) -> usize {
        self.0.chunking(data)
    }
}

#[derive(Default, Debug, Clone)]
pub struct NormalizedRabinChunking(pub RabinConfig);

impl SplitChunk for NormalizedRabinChunking {
    fn min_len(&self) -> usize {
        self.0.min_chunking_len()
    }
    fn split(&self, data:&[u8]) -> usize {
        self.0.normalized_chunking(data)
    }
}

#[derive(Default, Debug, Clone)]
pub struct FastcdcChunking(pub FastCDCConfig);

impl SplitChunk for FastcdcChunking {
    fn min_len(&self) -> usize {
        self.0.min_chunking_len()
    }
    fn split(&self, data:&[u8]) -> usize {
        self.0.chunking(data)
    }
}


#[derive(Default, Debug, Clone)]
pub struct AccelerateFastcdcChunking(pub FastCDCConfig);

impl SplitChunk for AccelerateFastcdcChunking {
    fn min_len(&self) -> usize {
        self.0.min_chunking_len()
    }
    fn split(&self, data:&[u8]) -> usize {
        self.0.accelerate_chunking(data)
    }
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct FixedBuilder {
    chunk_size:Option<usize>,
}

impl FixedBuilder {
    pub fn new() -> Self {
        Self { chunk_size:None }
    }
    
    pub fn chunk_size(&mut self, chunk_size:usize) -> &mut Self {
        self.chunk_size = Some(chunk_size);
        self
    }

    pub fn build(self) -> FixedChunking {
        let mut chunk_size = self.chunk_size.unwrap_or(4096);
        if chunk_size == 0 { chunk_size = 4096; }
        FixedChunking(chunk_size)
    }
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct FixedChunking(pub usize);

impl SplitChunk for FixedChunking {
    fn min_len(&self) -> usize {
        self.0
    }
    fn split(&self, data:&[u8]) -> usize {
        std::cmp::min(data.len(), self.0)
    }
}

#[test]
fn test_get_chunking_config() {
    // let chunking
    let config = config::Config::builder()
        .add_source(config::File::with_name("./chunk_io.toml"))
        .build()
        .unwrap();
    let chunking_builder:ChunkingBuilder = config.get("chunking").unwrap();
    println!("{:?}", chunking_builder);
    let chunking = chunking_builder.build();
    println!("{:?}", chunking);
}